
type spineTemp = Laya.Templet | Laya.SpineTemplet;

type spItem = Laya.Skeleton | Laya.SpineSkeleton;


export interface IShowSpine {

    /**父节点,默认为舞台 */
    parent?: Laya.Sprite;

    /**节点名字 */
    name?: string;

    /**节点位置 */
    pos?: { x: number, y: number };

    /**播放动作 */
    act?: string | number;

    /**播放声音 */
    sound?: string;

    /**是否清除旧的动画 default false */
    clearOldSpine?: boolean;

    /**使用缓存池 default: true */
    useCache?: boolean;

    /**播放完成自动清理,loop为true时无效 default: true */
    remove?: boolean;
}

export interface ISpineOption {

    /**循环 */
    loop?: boolean;

    /**回调 */
    call?: Function;

    /**相同动作，覆盖上一次 default: false */
    force?: boolean;

    /**动画播放起始时间 */
    start?: number;

    /**动画播放结束时间 */
    end?: number;

}

class SpineItem extends Laya.Sprite {

    private _spItem: spItem = null;
    private _act: string | number = null;
    private _option: ISpineOption = null;
    private _autoPause: boolean = true;
    private _active: boolean = false;

    constructor(public readonly spinePath: string, spItem?: spItem) {
        super();
        this._active = true;
        spItem && this.init(spItem);
        this.on(Laya.Event.REMOVED, this, this.onRemove);
        this.on(Laya.Event.ADDED, this, this.onAdd);
    }

    init(sp: spItem) {

        if (this.destroyed) {
            return;
        }

        this._spItem = sp;
        this.addChild(sp);

        if (this._act === null || typeof this._act === "undefined") {
            return;
        }

        this.tryPlay(this._act, this._option);
    }

    tryPlay(act?: string | number, option?: ISpineOption) {
        if (!this._active || this.destroyed) {
            return;
        }

        act = act || 0;

        if (!this._spItem) {
            this._act = act;
            this._option = option;
            return;
        }

        option = option || {};
        const loop = option.loop !== false;
        const force = option.force === true;
        const start = option.start || 0;
        const end = option.end || 0;

        this._spItem.play(act, loop, force, start, end, false, false);
        this.event(Laya.Event.START);

        //TODO destroy后scene没有destroy可能会报错
        // if (this.scene && this.scene instanceof Laya.Sprite && this._autoPause) {
        //     let s = this.scene as Laya.Sprite;
        //     s.off(Laya.Event.REMOVED, this, this.onRemove);
        //     s.off(Laya.Event.ADDED, this, this.onAdd);
        //     s.once(Laya.Event.REMOVED, this, this.onRemove);
        //     s.once(Laya.Event.ADDED, this, this.onAdd);
        // }

        if (loop || !option.call) {
            return;
        }

        this._spItem.once(Laya.Event.STOPPED, this, () => {
            option.call && option.call();
            option.call = null;
        });
    }

    /**
     * removechild 自动隐藏，tryplay之前调用才生效
     * @param autoPause 
     */
    setAutoPause(autoPause: boolean) {
        this._autoPause = autoPause;
    }

    stop() {
        this._act = this._option = null;
        if (this._spItem) {
            this._spItem.stop();
            this.event(Laya.Event.STOPPED);
        }
    }

    stopByTime(time: number) {
        if (!this._spItem) {
            return;
        }

        if (this._spItem instanceof Laya.SpineSkeleton) {
            this._spItem.currentTime = time;
        } else if (this._spItem instanceof Laya.Skeleton) {
            throw "stopByTime not support Skeleton";
        }
    }

    paused() {
        if (this._spItem) {
            this.log("暂停播放=======>", this.spinePath);
            this._spItem.paused();
        }
        this._act = this._option = null;
    }

    resume() {
        if (!this._active) {
            return;
        }

        if (this._spItem) {
            this.log("恢复播放=======>", this.spinePath);
            this._spItem.resume()
        }
    }

    destroy() {
        if (this.destroyed) {
            return;
        }
        this.log("destroy sk=======>", this.spinePath);

        this.event(Laya.Event.END);

        if (this._spItem) {
            this._spItem.offAll();
            this._spItem.stop();
        }

        this._spItem = null;
        this._act = null;
        this._option = null;
        this._active = false;
        super.destroy();

    }

    clear() {
        this.destroy();
    }

    setActive(active: boolean) {
        this._active = active;
    }

    get active() {
        return this._active;
    }

    private onRemove() {
        this._autoPause && this.paused();
    }

    private onAdd() {
        this._autoPause && this.resume();
    }

    private log(...str: any[]) {
        console.log("[--Spine--]", ...str);
    }
}

export class SpineManager {

    private spMap: Map<string, spineTemp | Promise<spineTemp>> = new Map<string, spineTemp | Promise<spineTemp>>();

    /**动画缓存池 */
    private spCacheList: Map<string, Set<SpineItem>> = new Map<string, Set<SpineItem>>();


    /**动画缓存池容量 */
    private readonly poolSize: number = 5;


    /**
     * 预加载动画
     * @param url 动画路径(全路径带后缀)
     * @param callBack 加载完成回调
     * @returns 
     */
    loadSpine(url: string, callBack?: Function) {

        return this.loadSkelOrSk(url).then(res => {
            if (callBack) {
                callBack(res);
                callBack = null;
            }
            return res;
        })
    }

    /**
     * 创建动画
     * @param url 动画路径(全路径带后缀)
     * @returns 
     */
    createSk(url: string, useCache?: boolean): SpineItem {

        if (useCache) {
            let sp = this.getCacheSk(url);
            if (sp) {
                return sp;
            }
        }

        let sp = new SpineItem(url);

        this.loadSkelOrSk(url).then(res => {
            if (sp.destroyed) {
                return;
            }
            sp.init(res.buildArmature())
        }).catch(err => {
            console.error(err);
            sp.destroy();

        })

        return sp;
    }


    /**
     * 显示动画
     * @param url 动画路径(全路径带后缀),自动识别是否是3.8的版本
     * @param option 
     * @returns SpineItem
     */
    showAnim(url: string, option?: IShowSpine & ISpineOption): SpineItem {

        option = option || {};
        const parent = option.parent || Laya.stage;
        const name = option.name || "spine_node";
        const useCache = option.useCache !== false;
        const remove = option.remove !== false;
        const clearOldSpine = !!option.clearOldSpine;
        const { x, y } = option.pos || { x: 0, y: 0 };

        let sp = parent.getChildByName(name) as SpineItem;

        if (!sp) {
            sp = this.createSk(url, useCache);

        } else if (!(sp instanceof SpineItem)) {
            //有其他节点
            throw "repetition node:" + url;

        } else if (clearOldSpine) {
            //清理旧spine
            this.removeSpine(sp);
            sp = null;

        } else if (sp.spinePath !== url) {
            //spine改变
            console.log("spine url change: \n", sp.spinePath + "====>" + url);
            this.removeSpine(sp);
            sp = null;

        }

        if (!sp) {
            sp = this.createSk(url, useCache);

        }

        if (remove) {
            let call = option.call;
            option.call = () => {
                call && call();
                this.removeSpine(sp);
            }
        }

        parent.addChild(sp);
        sp.name = name;
        sp.pos(x, y);
        sp.setActive(true);
        sp.tryPlay(option.act, option);

        return sp;
    }

    /**
     * 清理动画
     * @param parent 
     * @param name 
     * @returns 
     */
    clearAni(parent: Laya.Sprite, name?: string) {
        name = name || "spine_node";
        let sp = parent.getChildByName(name) as SpineItem;
        if (!sp) {
            return;
        }

        if (sp instanceof SpineItem) {
            // sp.clear();
            this.removeSpine(sp);
            return;
        }
    }

    /**
     * 清理缓存池
     * @param url 不传全清
     */
    clearCache(url?: string) {
        if (!url) {
            this.spCacheList.forEach(list => {
                if (!list) {
                    return;
                }

                list.forEach(v => v.clear());
            })
            this.spCacheList.clear();
            return;
        }

        let list = this.spCacheList.get(url);
        if (list) {
            list.forEach(sp => {
                sp.clear();
            })
            this.spCacheList.delete(url);
        }
    }


    /**
     * 销毁未引用资源
     */
    disposeUnreferenced() {
        this.spMap.forEach((v, k) => {
            if (v instanceof Promise) {
                return;
            }

            if (v.referenceCount <= 0) {
                v.destroy();
            }
        })
    }

    /**
     * 销毁所有动效资源
     */
    disposeAll() {
        this.spMap.forEach((v, k) => {
            if (v instanceof Promise) {
                return;
            }
            v.destroy();
        })
    }


    /**移除并缓存动画 */
    removeSpine(sp: SpineItem) {

        let list = this.spCacheList.get(sp.spinePath);
        if (!list) {
            list = new Set();
            this.spCacheList.set(sp.spinePath, list);
        } else if (list.size >= this.poolSize) {
            sp.clear();
            return;
        }
        console.log("removeSpine:", sp.spinePath);
        sp.setActive(false);
        sp.stop();
        sp.removeSelf();
        list.add(sp);
    }

    private getCacheSk(url: string) {
        let list = this.spCacheList.get(url);
        if (!list) {
            return null;
        }

        while (list.size > 0) {
            let sp: SpineItem = list.values().next().value;
            list.delete(sp);
            if (sp.destroyed || sp.active) {
                continue;
            }
            return sp;
        }

        return null;
    }

    /**
     * 加载动画
     * @param url 动画路径（全路径），基于后缀区分动画版本
     * @param version 
     * @returns 
     */
    private loadSkelOrSk(url: string, version: Laya.SpineVersion = Laya.SpineVersion.v3_8) {

        let templet: typeof Laya.SpineTemplet | typeof Laya.Templet = null;
        let type = Laya.Loader.BUFFER;
        if (url.endsWith(".skel")) {
            templet = Laya.SpineTemplet;

        } else if (url.endsWith(".json")) {
            templet = Laya.SpineTemplet;
            type = Laya.Loader.JSON;

        } else if (url.endsWith(".sk")) {
            templet = Laya.Templet;
            version = null;
        }

        if (!templet) {
            return Promise.reject("不支持的动画格式:" + url);
        }

        let cached = this.spMap.get(url);

        if (cached instanceof Promise) {
            return cached;
        }

        if (cached && !cached.destroyed) {
            return Promise.resolve(cached);
        }


        let loadPromise = new Promise<spineTemp>((resolve, reject) => {

            Laya.loader.load(url, Laya.Handler.create(this, (res) => {
                if (!res) {
                    this.spMap.delete(url);
                    reject("动画加载失败:" + url);
                    return;
                }

                let tp = new templet(version);
                tp.once(Laya.Event.ERROR, this, () => {
                    this.spMap.delete(url);
                    reject("动画加载失败1:" + url);
                });
                tp.once(Laya.Event.COMPLETE, this, () => {
                    this.spMap.set(url, tp);
                    resolve(tp);
                });
                tp.loadAni(url);

            }), null, type);

        })

        this.spMap.set(url, loadPromise);

        return loadPromise;
    }

}

const spm = new SpineManager();
export default spm;

window["spm"] = spm;